package com.myh.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.myh.gulimall.product.dao.CategoryBrandRelationDao;
import com.myh.gulimall.product.entity.CategoryBrandRelationEntity;
import com.myh.gulimall.product.service.CategoryBrandRelationService;
import com.myh.gulimall.product.vo.Catelog2Vo;
import com.myh.gulimall.product.vo.Catelog3Vo;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myh.common.utils.PageUtils;
import com.myh.common.utils.Query;

import com.myh.gulimall.product.dao.CategoryDao;
import com.myh.gulimall.product.entity.CategoryEntity;
import com.myh.gulimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryDao categoryDao;

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }


    @Override
    public List<CategoryEntity> listWithTree() {
        //1、查出所有分类
        List<CategoryEntity> entities = categoryDao.selectList(null);
        //2、组装成父子的树型结构
        //2.1 找到所有的一级分类
        List<CategoryEntity> level1Menus = entities.stream().filter(categoryEntity ->
                categoryEntity.getParentCid() == 0
        ).map((menu) -> {   //2.2找到所有菜单的子菜单
            menu.setChildren(getChildrens(menu, entities));
            return menu;
        }).sorted((menu1, menu2) -> {
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());
        return level1Menus;
    }

    @Override
    public void removeMenuByIds(List<Long> list) {
        //TODO 1、检查当前删除的菜单，是否被别的地方引用。只有在不被引用的情况下才可以被删除。（即删除的条件必须没有子菜单）
        //逻辑删除
        categoryDao.deleteBatchIds(list);
    }

    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> path = new ArrayList<>();
        List<Long> parentIds = findParentIds(catelogId, path);
        Collections.reverse(parentIds);
        return parentIds.toArray(new Long[parentIds.size()]);
    }

    @Override
    @Transactional  //涉及到多张表的操作。所以需要事务。
    @CacheEvict(value = {"category"}, key = "'getLevelOneCategorys'")
    public void updateDetail(CategoryEntity category) {
        categoryDao.updateById(category);
        //在其他数据库表当中更新其中的冗余字段
        if (!StringUtils.isEmpty(category.getName())) {
            categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
            //TODO 在其他数据库表当中更新其中的冗余字段
        }

    }

    //根据传进来的cid查询父子name组
    public String findCatelogPathName(Long catelogId) {
        Long[] catelogPath = findCatelogPath(catelogId);
        List<CategoryEntity> entities = categoryDao.selectBatchIds(Arrays.asList(catelogPath));
        String collect = entities.stream().map((entity) -> {
            String name = entity.getName();
            return name;
        }).collect(Collectors.joining("/"));
        return collect;
    }

    @Override
    @Cacheable(value = {"category"}, key = "#root.methodName", sync = true)
    public List<CategoryEntity> getLevelOneCategorys() {
        QueryWrapper<CategoryEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_cid", 0);
        List<CategoryEntity> categoryEntities = categoryDao.selectList(queryWrapper);
        return categoryEntities;
    }

    // TODO: 2024/3/19 加入redis缓存后会出现堆外内存溢出异常：OutOfDirectMemoryError
    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
        /*
         * 1、解决缓存穿透问题：空结果缓存
         * 2、解决缓存雪崩问题：设置加随机值的过期时间
         * 3、解决缓存击穿问题：加锁
         * */
        ValueOperations<String, String> stringStringValueOperations = stringRedisTemplate.opsForValue();
        String catalogJson = stringStringValueOperations.get("getCatalogJson");
        if (StringUtils.isEmpty(catalogJson)) {
            Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDbWithRedisLock();
            return catalogJsonFromDb;
        }
        TypeReference<Map<String, List<Catelog2Vo>>> typeReference = new TypeReference<Map<String, List<Catelog2Vo>>>() {
        };
        return JSON.parseObject(catalogJson, typeReference);
    }

    //使用Redisson框架来实现分布式锁
    private Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedissonLock() {
        RLock lock = redissonClient.getLock("CatalogJson_lock");
        lock.lock();
        Map<String, List<Catelog2Vo>> dataFromDb = null;
        try {
            dataFromDb = getDataFromDb();
        } finally {
            lock.unlock();
        }
        return dataFromDb;

    }

    //1、从数据库查询并封装分类数据
    //2、加分布式锁来解决缓存击穿
    private Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
        //占分布式锁，并设置过期时间来解决死锁问题
        String uuid = UUID.randomUUID().toString();
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        if (lock) {
            //加锁成功-->执行业务
            Map<String, List<Catelog2Vo>> dataFromDb = null;
            try {
                dataFromDb = getDataFromDb();
            } finally {
                //执行完毕-->释放锁
//            因为 获取uuid+对比uuid+释放锁 应该是原子操作，而Java代码无法保证，因此需要调用lua脚本
//            String lockValue = stringRedisTemplate.opsForValue().get("lock");
//            if (uuid.equals(lockValue)){    //因为加了过期时间，所有需要判断是否为自己的锁，只有自己的锁才能删
//                stringRedisTemplate.delete("lock");
//            }
                String script = "if redis call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
            }
            return dataFromDb;
        } else {
            //加锁失败-->重试
            //休眠100ms
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return getCatalogJsonFromDbWithRedisLock(); //自旋的方式
        }
    }

    private Map<String, List<Catelog2Vo>> getDataFromDb() {
        //再次确认缓存当中是否存在数据
        String catalogJson = stringRedisTemplate.opsForValue().get("getCatalogJson");
        if (!StringUtils.isEmpty(catalogJson)) {
            Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
            });
            return result;
        }
        //查询数据库
        List<CategoryEntity> levelOneCategorys = getLevelOneCategorys();
        Map<String, List<Catelog2Vo>> map = levelOneCategorys.stream().collect(Collectors.toMap(key -> key.getCatId() + "", val -> {
            Long cat1Id = val.getCatId();
            List<CategoryEntity> category2Entities = getCategoryEntitiesByPid(cat1Id);
            List<Catelog2Vo> catelog2Vos = null;
            if (category2Entities != null && category2Entities.size() != 0) {
                catelog2Vos = category2Entities.stream().map((category2Entity) -> {
                    Long cat2Id = category2Entity.getCatId();
                    List<CategoryEntity> category3Entities = getCategoryEntitiesByPid(cat2Id);
                    List<Catelog3Vo> catelog3Vos = null;
                    if (category3Entities != null && category3Entities.size() != 0) {
                        catelog3Vos = category3Entities.stream().map((category3Entity) -> {
                            Catelog3Vo catelog3Vo = new Catelog3Vo();
                            catelog3Vo.setCatalog2Id(cat2Id + "");
                            catelog3Vo.setName(category3Entity.getName());
                            catelog3Vo.setId(category3Entity.getCatId() + "");
                            return catelog3Vo;
                        }).collect(Collectors.toList());
                    }
                    Catelog2Vo catelog2Vo = new Catelog2Vo();
                    catelog2Vo.setCatalog3List(catelog3Vos);
                    catelog2Vo.setId(category2Entity.getCatId() + "");
                    catelog2Vo.setCatalog1Id(cat1Id + "");
                    catelog2Vo.setName(category2Entity.getName());
                    return catelog2Vo;
                }).collect(Collectors.toList());
            }

            return catelog2Vos;
        }));
        //将数据放入缓存当中
        stringRedisTemplate.opsForValue().set("getCatalogJson", JSON.toJSONString(map), 1, TimeUnit.DAYS);
        return map;
    }

    //查询指定父id下所有的子id
    private List<CategoryEntity> getCategoryEntitiesByPid(Long pid) {
        QueryWrapper<CategoryEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_cid", pid);
        List<CategoryEntity> categoryEntities = categoryDao.selectList(queryWrapper);
        return categoryEntities;
    }

    //递归查询父节点id
    private List<Long> findParentIds(Long catelogId, List<Long> path) {
        //将当前id添加到path当中。
        path.add(catelogId);
        CategoryEntity category = categoryDao.selectById(catelogId);
        if (category.getParentCid() != 0) {
            findParentIds(category.getParentCid(), path);
        }
        return path;
    }

    //递归查找所有菜单的子菜单
    private List<CategoryEntity> getChildrens(CategoryEntity root, List<CategoryEntity> all) {
        List<CategoryEntity> children = all.stream().filter((categoryEntity) -> {
            return categoryEntity.getParentCid().equals(root.getCatId());
        }).map(categoryEntity -> {
            categoryEntity.setChildren(getChildrens(categoryEntity, all));
            return categoryEntity;
        }).sorted((menu1, menu2) -> {
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());
        return children;
    }

}